#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#include <unistd.h>
#include <fcntl.h>
#include <signal.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <pwd.h>

#include "ysh.h"
#include "jc.h"
#include "la.h"
#include "parse.h"
#include "error.h"
#include "pool.h"
#include "encap.h"
#include "sig_handle.h"

//static int upper_job_id = 0;
int jc_status = 0;                              // The exit status of most recent foreground child process.
pid_t ysh_pid;

static void init_argv(int argc, char s_argv[][STRING_SIZE], char *d_argv[/*PROG_ARGS_NUM*/])
{
    int i;
    for (i = 0; i < argc; i++) {
#ifdef DEBUG
        printf("%s ", s_argv[i]);
#endif
        d_argv[i] = s_argv[i];
    }
#ifdef DEBUG
    printf("\n");
#endif
    d_argv[i] = NULL;
}

static Bool validate_exec(void)
{
    // Check if the executables are available.
    // 'x', if it is executable.
    // Or, execvp() will help with this.
    #define PATH_NUM 10
    typedef struct path_t {
        char ps[PATH_LEN << 1];             // Path string
        int psl;                            // Path string length
    } path_t;

    char env_path[PATH_LEN] = {0};
    char *tp, *ttp;
    path_t path[PATH_NUM];
    int path_num = 0;
    int i, j;
    Bool flag;

    strcpy(env_path, getenv("PATH"));
    tp = ttp = env_path;
    do {
        tp = strchr(ttp, ':');
        if (tp == NULL) {
            strcpy(path[path_num].ps, ttp);
            strcat(path[path_num].ps, "/");
            path[path_num].psl = strlen(ttp) + 1;
            break;
        }
        else {
            *(tp) = '\0';
            strcpy(path[path_num].ps, ttp);
            path[path_num].psl = tp - ttp + 1;
            strcat(path[path_num].ps, "/");
            ttp = ++tp;
        }
        path_num++;
    } while (TRUE);

    for (i = 0; i <= current_cmdline.pipe_num; i++) {
        //printf("%s\n", current_cmdline.parsed_cmdline[i][0]);
        if (strchr(current_cmdline.parsed_cmdline[i][0], '/') != NULL) {
            if (access(current_cmdline.parsed_cmdline[i][0], X_OK) != 0) {
                flag = FALSE;
            }
            else {
                //flag = TRUE;
                continue;
            }
        }
        else {
            flag = FALSE;
            for (j = 0; j < path_num; j++) {
                strcat(&path[j].ps[path[j].psl], current_cmdline.parsed_cmdline[i][0]);
                if (access(path[j].ps, X_OK) == 0) {
                    //printf("%s\n", current_cmdline.parsed_cmdline[i][0]);
                    flag = TRUE;
                    path[j].ps[path[j].psl] = '\0';
                    break;
                }
                path[j].ps[path[j].psl] = '\0';
            }
        }
        if (flag == FALSE) {
            sprintf(error_message, "File %s can not be loaded as an executable.\n", current_cmdline.parsed_cmdline[i][0]);
            error_msg(error_message); 
            return FALSE;
        }
    }
    return TRUE;
}

static Job *job_init(void)
{
    Job *job;

    job = insert_job();
    // Init.
    strcpy(job->jstring, current_cmdline.raw_string);
    job->jrpn = job->jpnum = current_cmdline.pipe_num + 1;
    // Input redirection validation.
    if (current_cmdline.in_redirect == TRUE) {
        job->jfd[0] = open(current_cmdline.in_redirect_path, O_RDONLY);
        if (job->jfd[0] < 0) {
            error_msg("Open input redirection file failed.\n");
            delete_job(job);
            return FALSE;
        }
    }
    else {
        // No running in background check.
        job->jfd[0] = STDIN_FILENO;
    }
    // Output redirection validation.
    if (current_cmdline.out_redirect == TRUE) {
        job->jfd[1] = open(current_cmdline.out_redirect_path, O_WRONLY | O_CREAT);
        if (job->jfd[1] < 0) {
            error_msg("Open output redirection file failed.\n");
            delete_job(job);
            return FALSE;
        }
    }
    else {
        // No running in background check.
        job->jfd[1] = STDOUT_FILENO;
    }
    if (validate_exec() == FALSE) {
        delete_job(job);
        return (Job *)NULL;
    }

    return job;
}

void jc_init(void)
{
   ysh_pid = getpid();
}

void jc_exit()
{
    int wait_stat_val;
    // Kill child processes.
    pool_kill_all();
    ec_waitpid(-1, &wait_stat_val, WNOHANG, WAITPID_BG);
#ifdef DEBUG
    restore_back();
#else
    exit(EXIT_SUCCESS);
#endif
}

void jc_jobs(void)
{
    pool_print_jobs();
}

void jc_echo(void)
{
    int i;
    for (i = 0; i < current_cmdline.prog_args_num[0]; i++) {
        printf("%s ", current_cmdline.parsed_cmdline[0][i]);
    }
    putchar('\n');
}

void jc_fg(TokenType tt, char *id)
{
    int jid;
    Job *job;
    jid = atoi(&id[1]);
    job = find_job(jid);
    ec_tcsetpgrp(TERM_FILEDES, job->jgid);
    kill(-job->jgid, SIGCONT);
}

void jc_bg(TokenType tt, char *id)
{
    int jid;
    Job *job;
    jid = atoi(&id[1]);
    job = find_job(jid);
    kill(-job->jgid, SIGCONT);
}

void jc_history(void)
{
    pool_print_history();
}

void jc_cd(void)
{
    char *dir_str;
    dir_str = current_cmdline.parsed_cmdline[0][0];
    // Just "cd EOL".
    if (strlen(dir_str) == 0) {
        return;
    }
    // "cd ~".
    if (strcmp(dir_str, HOME_DIRECTORY) == 0) {
        strcpy(dir_str, getpwuid(getuid())->pw_dir);
    }
    // "cd TT_STRING".
    if (chdir(dir_str) < 0) {
        sprintf(error_message, "Directory \'%s\' was not found.\n", dir_str);
        error_msg(error_message);
    }
}

void jc_exec(void)
{
    char *argv[PROG_ARGS_NUM] = {0};

    if (validate_exec() == TRUE) {
        init_argv(current_cmdline.prog_args_num[0], current_cmdline.parsed_cmdline[0], argv);
        execvp(argv[0], argv);
    }
    else {
        sprintf(error_message, "File %s cannot be loaded as an valid executable.\n", current_cmdline.parsed_cmdline[0][0]);
    }
}

// Redirection and pipeline.
// Error-prone here, I do not want to check them one by one...
void jc_start_job(void)
{
    Job *job;
    int i = 0;
    pid_t pgrpid;
    pid_t cpid;
    char *argv[PROG_ARGS_NUM] = {0};
    int pfd[2];
    int pre_pipe;
    Bool job_in_bg;
    int wait_stat_val;

    if ((job = job_init()) == NULL) {
        return;
    }
    // Run in background should be taken care of.
    job_in_bg = current_cmdline.run_in_bg;
    sig_reset();
    for (i = 0; i < job->jpnum; i++) {
        init_argv(current_cmdline.prog_args_num[i], current_cmdline.parsed_cmdline[i], argv);
        if (ec_pipe(pfd) < 0) {
            return;
        }
        cpid = fork();
        // Process group id for new process.
        if (i == 0) {
            job->jgid = cpid;
            pgrpid = cpid;
        }
        if (cpid < 0) {
            error_msg("Fork new process failed.\n");
            return;
        }
        else if (cpid == 0) {
            setpgid(0, pgrpid);
            // The first process in pipeline.
            if (i == 0) {
                dup2(job->jfd[0], STDIN_FILENO);
            }
            else {
                dup2(pre_pipe, STDIN_FILENO);
            }
            close(pfd[0]);
            // The last process in pipeline.
            if (i == job->jpnum - 1) {
                dup2(job->jfd[1], STDOUT_FILENO);
                close(pfd[1]);
            }
            else {
                dup2(pfd[1], STDOUT_FILENO);
            }
            if (job_in_bg == FALSE && i == 0) {
                signal(SIGTTOU, SIG_IGN);
                ec_tcsetpgrp(TERM_FILEDES, getpid());
                signal(SIGTTOU, SIG_DFL);
            }
            execvp(argv[0], argv);
        }
        else {
            setpgid(cpid, pgrpid);
            insert_p(cpid, job);
            if (i != 0) {
                close(pre_pipe);
            }
            pre_pipe = pfd[0];
            close(pfd[1]);
        }
    }
    sig_init();
    if (job_in_bg == FALSE) {
        ec_waitpid(-pgrpid, &wait_stat_val, WUNTRACED, WAITPID_FG);
    }
    ec_waitpid(-1, &wait_stat_val, WNOHANG | WUNTRACED | WCONTINUED, WAITPID_BG);
}

